%%%-------------------------------------------------------------------
%%% @doc Handles the publishing of applications from local to a 
%%%      versioned  code repository.
%%% 
%%% Types:
%%%  @type repo() = string(). Contains address and repo designation. 
%%%   Example: http://www.erlware.org/stable   
%%%  @type repo_suffix() = string(). Contains ErtsVsn/Area/Application/Vsn/TarFile.
%%%  @type timeout() = integer() | infinity. Timeouts are specified in milliseconds.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(fax_publish).

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
	 publish/4,
	 publish/3,
	 publish/6,
	 publish/5
	 ]).


%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("faxien.hrl").

%%====================================================================
%% External functions
%%====================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc 
%%  Publish a release or appliation to a repository. The PackageDir must be formatted in the following 
%%  way &lt;relname&gt;-&lt;rel version&gt; If you are publishing a tarball it must be compressed and have 
%%  the extention .tar.gz.
%%
%% @spec publish(IsGuarded, Repos, PackageDir, Timeout::timeout()) -> ok | {error, Reason}
%% where
%%     IsGuarded = bool() 
%%     Repos = [repo()] 
%%     PackageDir = string() 
%% @end
%%--------------------------------------------------------------------
publish(IsGuarded, Repos, PackageDir, Timeout) -> 
    case regexp:match(PackageDir, ".*.tar.gz") of
	{match, _, _} ->
	    TmpPackageDir = fax_util:stage_package_tarball(PackageDir),
	    publish(IsGuarded, Repos, TmpPackageDir, Timeout);
	_NoMatch ->
	    Type = get_type(PackageDir),
	    ?INFO_MSG("publishing a package of type: ~p~n", [Type]), 
	    Res = publish(IsGuarded, Type, Repos, PackageDir, Timeout),
	    fax_util:delete_potential_package_tmp_dir(PackageDir),
	    Res
    end.

%% @spec publish(Repos, PackageDir, Timeout::timeout()) -> ok | {error, Reason}
%% @equiv publish(false, Repos, PackageDir, Timeout)
publish(Repos, PackageDir, Timeout) -> 
    publish(false, Repos, PackageDir, Timeout).


%%--------------------------------------------------------------------
%% @doc publish files external to an application to a repository, for example a directory containing documentation for 
%%      an application.
%% <pre>
%% Example:
%%  publish(doc, "http://www.erlware.org/stable", my_app, '1.3.5', "/home/jdoe/my_proj/lib/my_app_doc", 10000).
%% </pre>
%%
%% @type publish_type() = doc.
%%       A guarded repo is a repo that will verify an app before including it, all submissions to guarded repo's must contain all 
%%       source files. unguarded repos allow applications to be placed directly with no verification phase.
%%
%% @spec publish(PublishType::publish_type(), Repos, Name, Vsn, Target, Timeout::timeout()) -> ok | {error, Reason}
%% where
%%     Repos = [repo()]
%%     Name = string()
%%     Vsn = string()
%%     Target = string()
%% @end
%%--------------------------------------------------------------------
publish(doc, _Repos, _AppName, _AppVsn, TargetDir, _Timeout) -> 
    gather_doc_files([TargetDir]).
    
    
    

%%====================================================================
%% Internal functions
%%====================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc publish a local application to a repository. 
%%      A guarded repo is a repo that will verify an app before including it, all submissions to guarded repo's must contain all 
%%      source files. unguarded repos allow applications to be placed directly with no verification phase.
%% <pre>
%% Example:
%%  publish(guarded, "http://www.erlware.org/stable", "/home/jdoe/my_proj/lib/my_app").
%% </pre>
%%
%% @type publish_type() = generic | binary | source | release | erts.  
%%
%% @spec publish(IsGuarded, PublishType, Repos, AppDir, Timeout::timeout()) -> ok | {error, Reason}
%% where
%%     IsGuarded = bool()
%%     PublishType = publish_type()
%%     Repos = [repo()]
%%     AppDir = string()
%% @end
%%--------------------------------------------------------------------
publish(IsGuarded, Type, RawRepos, RawAppDir, Timeout) -> 
    [Repos]   = fax_util:if_atom_to_string([RawRepos]),
    [AppDir] = fax_util:if_atom_to_string([RawAppDir]),
    case catch publish2(IsGuarded, Type, Repos, AppDir, Timeout) of
	{error, _Reason} = Res ->
	    ?INFO_MSG("publish(~p, ~p, ~p, ~p) -> ~p~n", [IsGuarded, Type, Repos, AppDir, Res]),
	    Res;
	{'EXIT', Reason} = Res ->
	    ?INFO_MSG("publish(~p, ~p, ~p, ~p) -> ~p~n", [IsGuarded, Type, Repos, AppDir, Res]),
	    {error, Reason};
	{ok, URLS} ->
	    io:format("publish/4 publishing to ~p~n", [URLS]), 
	    ok
    end.

publish2(false, erts, Repos, ErtsDir, Timeout) -> 
    ewr_put:put_erts_package(Repos, ErtsDir, Timeout); 

publish2(true, generic, Repos, AppDir, Timeout) -> 
    put_guarded_app(Repos, AppDir, Timeout); 
publish2(false, generic, Repos, AppDir, Timeout) -> 
    ewr_put:put_generic_package(Repos, AppDir, Timeout); 

publish2(true, binary, Repos, AppDir, Timeout) -> 
    put_guarded_app(Repos, AppDir, Timeout); 
publish2(false, binary, Repos, AppDir, Timeout) -> 
    ewr_put:put_binary_package(Repos, AppDir, Timeout); 

publish2(false, source, Repos, AppDir, Timeout) -> 
    ewr_put:put_source_package(Repos, AppDir, Timeout); 

publish2(true, release, Repos, RelDir, Timeout) -> 
    put_guarded_release(Repos, RelDir, Timeout);
publish2(false, release, Repos, RelDir, Timeout) -> 
    case filelib:is_dir(RelDir ++ "/lib") of
	true ->
	    ?INFO_MSG("ignoring the lib dir when publishing of ~p~n", [RelDir]),
	    {ok, TmpPackageDir} = fax_util:create_tmp_package_dir(),
	    TmpRelDir           = lists:flatten([TmpPackageDir, filename:basename(RelDir)]),
	    ewl_file:copy_dir(RelDir, TmpRelDir),
	    ok = ewl_file:delete_dir(TmpRelDir ++ "/lib"),
	    Resp = ewr_put:put_release_package(Repos, TmpRelDir, Timeout), 
	    ok = ewl_file:delete_dir(TmpPackageDir),
	    Resp;
	false ->
	    ewr_put:put_release_package(Repos, RelDir, Timeout) 
    end.
	    


%%--------------------------------------------------------------------
%% @private
%% @doc Determine the type of the package that is being published. 
%% @spec get_type(PackageDir) -> binary | generic | release | {error, Reason}
%% @end
%%--------------------------------------------------------------------
get_type(PackageDir) ->
    case fax_validation:is_package_erts(PackageDir) of
	true ->
	    erts;
	false -> 
	    case fax_validation:is_package_an_app(PackageDir) of
		true ->
		    case fax_validation:is_package_a_binary_app(PackageDir) of
			true  -> binary;
			false -> generic
		    end;
		false ->
		    case fax_validation:is_package_a_release(PackageDir) of
			true  -> release;
			false -> {error, not_a_valid_app_or_release}
		    end
	    end
    end.
	    
	    
%%--------------------------------------------------------------------
%% @private
%% @doc collect the directory structure of the doc directory.
%% @end
%%--------------------------------------------------------------------
gather_doc_files([H|T]) ->    
    case filelib:is_dir(H) of
	true  -> 
	    gather_doc_files(filelib:wildcard(H ++ "/*")) ++ 
	    gather_doc_files(T);
	false -> 
	    [H|gather_doc_files(T)]
    end;
gather_doc_files([]) ->    
    [].
		
    
%%--------------------------------------------------------------------
%% @private
%% @doc Collect data from the user about things that are not in the 
%% .app file for an application. 
%% <pre>
%% Types:
%%  MetaInfo = [{Key, Value}]
%%   Key = keywords
%% </pre>
%% @spec collect_package_meta_info() -> {ok, MetaInfo}
%% @end
%%--------------------------------------------------------------------
collect_package_meta_info() ->
    {ok, [
	  {author, ewl_talk:ask("Enter the authors full name > ")},
	  {email, ewl_talk:ask("Enter a contact email address > ")},
	  {keywords, string:tokens(ewl_talk:ask("Enter comma separated keywords for the package > "), ",")}
	 ]}.

%%--------------------------------------------------------------------
%% @private
%% @spec verify_presence_of_source_files(AppDir::string()) -> ok | {error, Reason}
%% @end
%%--------------------------------------------------------------------
verify_presence_of_source_files(AppDir) ->
    {ok, [{modules, Modules}]} = 
	ewr_util:fetch_local_appfile_key_values(AppDir, [modules]),
    ?INFO_MSG("verifying the presense of modules: ~p~n", [Modules]),
    F = fun(Mod) -> 
		?INFO_MSG("~p.erl present is ~p~n", [Mod, filelib:is_file(AppDir ++ "/src/" ++ atom_to_list(Mod) ++ ".erl")]) 
	end, 
    case catch lists:foreach(F, Modules) of
	ok     -> ok;
	Error -> {error, {"missing source files", Error}}
    end.
	    
put_guarded_release(Repos, RawRelDir, Timeout) -> 
    RelDir = filename:absname(RawRelDir),
    case regexp:match(Repos, "(.*://[^\/]*)/staging") of
	{match, _, _} ->
	    {ok, MetaInfo}       = collect_package_meta_info(),
	    {ok, {RelName, VSN}} = fax_paths:package_dir_to_name_and_vsn(RelDir),
	    [Suffix]             = ewr_util:gen_metadata_suffix(RelName, VSN),
	    Res = ewr_put:put_release_package(Repos, RelDir), 
	    ewr_put:repo_put(Repos, Suffix ++ "/" ++ RelName ++ ".release_info", 
			     list_to_binary(fax_util:flatten_term(MetaInfo)), Timeout), 
	    Res;
	_Error ->
	    {error, "the repo speficied is invalid. Guarded repos URLs must be of the form http://<path>/staging"}
    end.

put_guarded_app(Repos, RawAppDir, Timeout) -> 
    AppDir = filename:absname(RawAppDir),
    case regexp:match(Repos, "(.*://[^\/]*)/staging") of
	{match, _, _} ->
	    case verify_presence_of_source_files(AppDir) of
		ok -> 
		    {ok, MetaInfo}     = collect_package_meta_info(),
		    {ok, [{vsn, VSN}]} = ewr_util:fetch_local_appfile_key_values(AppDir, [vsn]),
		    AppName            = ewr_util:app_name(AppDir),
		    [Suffix]           = ewr_util:gen_metadata_suffix(AppName, VSN),
		    Res = ewr_put:put_source_package(Repos, AppDir), 
		    MetaInfoString = list_to_binary(fax_util:flatten_term(MetaInfo)),
		    ewr_put:repo_put(Repos, lists:flatten([Suffix, "/", AppName, ".app_info"]), MetaInfoString, Timeout), 
		    Res;
		Error ->
		    Error
	    end;
	_Error ->
	    {error, "the repo speficied is invalid. Guarded repos URLs must be of the form http://<path>/staging"}
    end.
